Package org.pushingpixels.flamingo.internal.ui.bcb

Source Code of org.pushingpixels.flamingo.internal.ui.bcb.BasicBreadcrumbBarUI$BreadcrumbBarLayout

/*
* Copyright (c) 2003-2010 Flamingo Kirill Grouchnikov
* and <a href="http://www.topologi.com">Topologi</a>.
* Contributed by <b>Rick Jelliffe</b> of <b>Topologi</b>
* in January 2006. in All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
*  o Redistributions of source code must retain the above copyright notice,
*    this list of conditions and the following disclaimer.
*    
*  o Redistributions in binary form must reproduce the above copyright notice,
*    this list of conditions and the following disclaimer in the documentation
*    and/or other materials provided with the distribution.
*    
*  o Neither the name of Flamingo Kirill Grouchnikov Topologi nor the names of
*    its contributors may be used to endorse or promote products derived
*    from this software without specific prior written permission.
*    
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.pushingpixels.flamingo.internal.ui.bcb;

import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

import javax.swing.*;
import javax.swing.border.EmptyBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;

import org.pushingpixels.flamingo.api.bcb.*;
import org.pushingpixels.flamingo.api.common.*;
import org.pushingpixels.flamingo.api.common.JCommandButton.CommandButtonKind;
import org.pushingpixels.flamingo.api.common.JCommandButton.CommandButtonPopupOrientationKind;
import org.pushingpixels.flamingo.api.common.icon.EmptyResizableIcon;
import org.pushingpixels.flamingo.api.common.icon.ResizableIcon;
import org.pushingpixels.flamingo.api.common.model.PopupButtonModel;
import org.pushingpixels.flamingo.api.common.popup.*;
import org.pushingpixels.flamingo.internal.utils.FlamingoUtilities;

/**
* Basic UI for breadcrumb bar ({@link JBreadcrumbBar}).
*
* @author Topologi
* @author Kirill Grouchnikov
* @author Pawel Hajda
*/
public class BasicBreadcrumbBarUI extends BreadcrumbBarUI {
  /**
   * The associated breadcrumb bar.
   */
  protected JBreadcrumbBar breadcrumbBar;

  protected JPanel mainPanel;

  protected JScrollablePanel<JPanel> scrollerPanel;

  protected ComponentListener componentListener;

  protected JCommandButton dummy;

  /**
   * Contains the item path.
   */
  protected LinkedList modelStack;

  protected LinkedList<JCommandButton> buttonStack;

  protected BreadcrumbPathListener pathListener;

  private AtomicInteger atomicCounter;

  /*
   * (non-Javadoc)
   *
   * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
   */
  public static ComponentUI createUI(JComponent c) {
    return new BasicBreadcrumbBarUI();
  }

  /*
   * (non-Javadoc)
   *
   * @see javax.swing.plaf.ComponentUI#installUI(javax.swing.JComponent)
   */
  @Override
  public void installUI(JComponent c) {
    this.breadcrumbBar = (JBreadcrumbBar) c;

    this.modelStack = new LinkedList();
    this.buttonStack = new LinkedList<JCommandButton>();

    installDefaults(this.breadcrumbBar);
    installComponents(this.breadcrumbBar);
    installListeners(this.breadcrumbBar);

    c.setLayout(createLayoutManager());

    if (this.breadcrumbBar.getCallback() != null) {
      SwingWorker<List<StringValuePair>, Void> worker = new SwingWorker<List<StringValuePair>, Void>() {
        @Override
        protected List<StringValuePair> doInBackground()
            throws Exception {
          return breadcrumbBar.getCallback().getPathChoices(null);
        }

        @Override
        protected void done() {
          try {
            pushChoices(new BreadcrumbItemChoices(null, get()));
          } catch (Exception exc) {
          }
        }
      };
      worker.execute();
    }

    this.dummy = new JCommandButton("Dummy", new EmptyResizableIcon(16));
    this.dummy.setDisplayState(CommandButtonDisplayState.MEDIUM);
    this.dummy
        .setCommandButtonKind(CommandButtonKind.ACTION_AND_POPUP_MAIN_ACTION);
  }

  /*
   * (non-Javadoc)
   *
   * @see javax.swing.plaf.ComponentUI#uninstallUI(javax.swing.JComponent)
   */
  @Override
  public void uninstallUI(JComponent c) {
    c.setLayout(null);

    uninstallListeners((JBreadcrumbBar) c);
    uninstallComponents((JBreadcrumbBar) c);
    uninstallDefaults((JBreadcrumbBar) c);
    this.breadcrumbBar = null;
  }

  protected void installDefaults(JBreadcrumbBar bar) {
    Font currFont = bar.getFont();
    if ((currFont == null) || (currFont instanceof UIResource)) {
      Font font = FlamingoUtilities.getFont(null, "BreadcrumbBar.font",
          "Button.font", "Panel.font");
      bar.setFont(font);
    }
  }

  protected void installComponents(JBreadcrumbBar bar) {
    this.mainPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0));
    this.mainPanel.setBorder(new EmptyBorder(0, 0, 0, 0));
    this.mainPanel.setOpaque(false);
    this.scrollerPanel = new JScrollablePanel<JPanel>(this.mainPanel,
        JScrollablePanel.ScrollType.HORIZONTALLY);

    bar.add(this.scrollerPanel, BorderLayout.CENTER);
  }

  protected void installListeners(final JBreadcrumbBar bar) {
    this.atomicCounter = new AtomicInteger(0);

    this.componentListener = new ComponentAdapter() {
      @Override
      public void componentResized(ComponentEvent e) {
        updateComponents();
      }
    };
    bar.addComponentListener(this.componentListener);

    this.pathListener = new BreadcrumbPathListener() {
      private SwingWorker<Void, Object> pathChangeWorker;

      @Override
      public void breadcrumbPathEvent(BreadcrumbPathEvent event) {
        final int indexOfFirstChange = event.getIndexOfFirstChange();

        if ((this.pathChangeWorker != null)
            && !this.pathChangeWorker.isDone()) {
          this.pathChangeWorker.cancel(true);
        }
        this.pathChangeWorker = new SwingWorker<Void, Object>() {
          @Override
          protected Void doInBackground() throws Exception {
            atomicCounter.incrementAndGet();

            synchronized (BasicBreadcrumbBarUI.this) {
              // remove stack elements after the first change
              if (indexOfFirstChange == 0) {
                modelStack.clear();
              } else {
                int toLeave = indexOfFirstChange * 2 + 1;
                while (modelStack.size() > toLeave)
                  modelStack.removeLast();
              }
            }

            SwingUtilities.invokeLater(new Runnable() {
              @Override
              public void run() {
                updateComponents();
              }
            });

            if (indexOfFirstChange == 0) {
              List<StringValuePair> rootChoices = breadcrumbBar
                  .getCallback().getPathChoices(null);
              BreadcrumbItemChoices bic = new BreadcrumbItemChoices(
                  null, rootChoices);
              if (!this.isCancelled()) {
                publish(bic);
              }
            }

            List<BreadcrumbItem> items = breadcrumbBar.getModel()
                .getItems();
            if (items != null) {
              for (int itemIndex = indexOfFirstChange; itemIndex < items
                  .size(); itemIndex++) {
                if (this.isCancelled())
                  break;

                BreadcrumbItem item = items.get(itemIndex);
                publish(item);

                // now check if it has any children
                List<BreadcrumbItem> subPath = new ArrayList<BreadcrumbItem>();
                for (int j = 0; j <= itemIndex; j++) {
                  subPath.add(items.get(j));
                }
                BreadcrumbItemChoices bic = new BreadcrumbItemChoices(
                    item, breadcrumbBar.getCallback()
                        .getPathChoices(subPath));
                if ((bic.getChoices() != null)
                    && (bic.getChoices().length > 0)) {
                  // add the selector - the current item has
                  // children
                  publish(bic);
                }
              }
            }
            return null;
          }

          @Override
          protected void process(List<Object> chunks) {
            if (chunks != null) {
              for (Object chunk : chunks) {
                if (this.isCancelled()
                    || atomicCounter.get() > 1)
                  break;

                if (chunk instanceof BreadcrumbItemChoices) {
                  pushChoices((BreadcrumbItemChoices) chunk,
                      false);
                }
                if (chunk instanceof BreadcrumbItem) {
                  pushChoice((BreadcrumbItem) chunk, false);
                }
              }
            }
            updateComponents();
          }

          @Override
          protected void done() {
            atomicCounter.decrementAndGet();
          }
        };
        pathChangeWorker.execute();
      }
    };
    this.breadcrumbBar.getModel().addPathListener(this.pathListener);
  }

  protected void uninstallDefaults(JBreadcrumbBar bar) {
  }

  protected void uninstallComponents(JBreadcrumbBar bar) {
    this.mainPanel.removeAll();
    this.buttonStack.clear();

    bar.remove(this.scrollerPanel);
  }

  protected void uninstallListeners(JBreadcrumbBar bar) {
    bar.removeComponentListener(this.componentListener);
    this.componentListener = null;

    this.breadcrumbBar.getModel().removePathListener(this.pathListener);
    this.pathListener = null;
  }

  /**
   * Invoked by <code>installUI</code> to create a layout manager object to
   * manage the {@link JBreadcrumbBar}.
   *
   * @return a layout manager object
   *
   * @see BreadcrumbBarLayout
   */
  protected LayoutManager createLayoutManager() {
    return new BreadcrumbBarLayout();
  }

  /**
   * Layout for the breadcrumb bar.
   *
   * @author Kirill Grouchnikov
   * @author Topologi
   */
  protected class BreadcrumbBarLayout implements LayoutManager {
    /**
     * Creates new layout manager.
     */
    public BreadcrumbBarLayout() {
    }

    /*
     * (non-Javadoc)
     *
     * @see java.awt.LayoutManager#addLayoutComponent(java.lang.String,
     * java.awt.Component)
     */
    public void addLayoutComponent(String name, Component c) {
    }

    /*
     * (non-Javadoc)
     *
     * @see java.awt.LayoutManager#removeLayoutComponent(java.awt.Component)
     */
    public void removeLayoutComponent(Component c) {
    }

    /*
     * (non-Javadoc)
     *
     * @see java.awt.LayoutManager#preferredLayoutSize(java.awt.Container)
     */
    public Dimension preferredLayoutSize(Container c) {
      // The height of breadcrumb bar is
      // computed based on the preferred height of a command
      // button in MEDIUM state.
      int buttonHeight = dummy.getPreferredSize().height;

      Insets ins = c.getInsets();
      return new Dimension(c.getWidth(), buttonHeight + ins.top
          + ins.bottom);
    }

    /*
     * (non-Javadoc)
     *
     * @see java.awt.LayoutManager#minimumLayoutSize(java.awt.Container)
     */
    public Dimension minimumLayoutSize(Container c) {
      int buttonHeight = dummy.getPreferredSize().height;

      return new Dimension(10, buttonHeight);
    }

    /*
     * (non-Javadoc)
     *
     * @see java.awt.LayoutManager#layoutContainer(java.awt.Container)
     */
    public void layoutContainer(Container c) {
      int width = c.getWidth();
      int height = c.getHeight();
      scrollerPanel.setBounds(0, 0, width, height);
    }
  }

  protected synchronized void updateComponents() {
    if (!this.breadcrumbBar.isVisible())
      return;

    this.mainPanel.removeAll();
    buttonStack.clear();
    // update the ui
    for (int i = 0; i < modelStack.size(); i++) {
      Object element = modelStack.get(i);
      if (element instanceof BreadcrumbItemChoices) {
        BreadcrumbItemChoices bic = (BreadcrumbItemChoices) element;
        if (buttonStack.isEmpty()) {
          JCommandButton button = new JCommandButton("");
          button.setCommandButtonKind(CommandButtonKind.POPUP_ONLY);
          configureBreadcrumbButton(button);
          configurePopupAction(button, bic);
          configurePopupRollover(button);
          buttonStack.add(button);
        } else {
          JCommandButton button = buttonStack.getLast();
          int oldW = button.getPreferredSize().width;
          button
              .setCommandButtonKind(CommandButtonKind.ACTION_AND_POPUP_MAIN_ACTION);
          configurePopupAction(button, bic);
          configurePopupRollover(button);
        }
      } else if (element instanceof BreadcrumbItem) {
        BreadcrumbItem bi = (BreadcrumbItem) element;

        JCommandButton button = new JCommandButton(bi.getKey());
        configureBreadcrumbButton(button);
        configureMainAction(button, bi);
        final Icon icon = bi.getIcon();
        if (icon != null) {
          button.setIcon(new ResizableIcon() {
            int iw = icon.getIconWidth();
            int ih = icon.getIconHeight();

            @Override
            public void paintIcon(Component c, Graphics g, int x,
                int y) {
              int dx = (iw - icon.getIconWidth()) / 2;
              int dy = (ih - icon.getIconHeight()) / 2;
              icon.paintIcon(c, g, x + dx, y + dy);
            }

            @Override
            public int getIconWidth() {
              return iw;
            }

            @Override
            public int getIconHeight() {
              return ih;
            }

            @Override
            public void setDimension(Dimension newDimension) {
              iw = newDimension.width;
              ih = newDimension.height;
            }
          });
        }

        if (i > 0) {
          BreadcrumbItemChoices lastBic = (BreadcrumbItemChoices) modelStack
              .get(i - 1);
          BreadcrumbItem[] choices = lastBic.getChoices();
          if (choices != null) {
            for (int j = 0; j < choices.length; j++) {
              if (bi.getKey().equals(choices[j].getKey())) {
                lastBic.setSelectedIndex(j);
                break;
              }
            }
          }
        }

        buttonStack.addLast(button);
      }
    }

    for (JCommandButton jcb : buttonStack) {
      this.mainPanel.add(jcb);
    }

    this.scrollerPanel.revalidate();
    this.scrollerPanel.repaint();
    SwingUtilities.invokeLater(new Runnable() {
      @Override
      public void run() {
        // scroll to the last element in the breadcrumb bar
        scrollerPanel.scrollToIfNecessary(
            mainPanel.getPreferredSize().width, 0);
        scrollerPanel.repaint();
      }
    });
  }

  private void configureMainAction(JCommandButton button,
      final BreadcrumbItem bi) {
    button.getActionModel().addActionListener(new ActionListener() {
      @Override
      public void actionPerformed(ActionEvent e) {
        SwingUtilities.invokeLater(new Runnable() {
          @Override
          public void run() {
            BreadcrumbBarModel barModel = breadcrumbBar.getModel();
            int itemIndex = barModel.indexOf(bi);
            int toLeave = (itemIndex < 0) ? 0 : itemIndex + 1;
            barModel.setCumulative(true);
            while (barModel.getItemCount() > toLeave) {
              barModel.removeLast();
            }
            barModel.setCumulative(false);
          }
        });
      }
    });
  }

  private void configurePopupAction(JCommandButton button,
      final BreadcrumbItemChoices bic) {
    button.setPopupCallback(new PopupPanelCallback() {
      @Override
      public JPopupPanel getPopupPanel(JCommandButton commandButton) {
        JCommandPopupMenu popup = new JCommandPopupMenu();
        for (int i = 0; i < bic.getChoices().length; i++) {
          final BreadcrumbItem bi = bic.getChoices()[i];

          JCommandMenuButton menuButton = new JCommandMenuButton(bi
              .getKey(), null);
          final Icon icon = bi.getIcon();
          if (icon != null) {
            menuButton.setIcon(new ResizableIcon() {
              int iw = icon.getIconWidth();
              int ih = icon.getIconHeight();

              @Override
              public void paintIcon(Component c, Graphics g,
                  int x, int y) {
                int dx = (iw - icon.getIconWidth()) / 2;
                int dy = (ih - icon.getIconHeight()) / 2;
                icon.paintIcon(c, g, x + dx, y + dy);
              }

              @Override
              public int getIconWidth() {
                return iw;
              }

              @Override
              public int getIconHeight() {
                return ih;
              }

              @Override
              public void setDimension(Dimension newDimension) {
                iw = newDimension.width;
                ih = newDimension.height;
              }
            });
          }
          if (i == bic.getSelectedIndex()) {
            menuButton.setFont(menuButton.getFont().deriveFont(
                Font.BOLD));
          }

          final int biIndex = i;
          menuButton.getActionModel().addActionListener(
              new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                  SwingUtilities.invokeLater(new Runnable() {
                    public void run() {
                      if (bi == null)
                        return;

                      BreadcrumbBarModel barModel = breadcrumbBar
                          .getModel();
                      barModel.setCumulative(true);
                      int itemIndex = barModel
                          .indexOf(bic.getAncestor());
                      int toLeave = ((bic.getAncestor() == null) || (itemIndex < 0)) ? 0
                          : itemIndex + 1;
                      while (barModel.getItemCount() > toLeave) {
                        barModel.removeLast();
                      }
                      barModel.addLast(bi);

                      bic.setSelectedIndex(biIndex);

                      barModel.setCumulative(false);
                    }
                  });
                }
              });

          popup.addMenuButton(menuButton);
          menuButton.setCursor(Cursor
              .getPredefinedCursor(Cursor.HAND_CURSOR));
        }
        popup.setMaxVisibleMenuButtons(10);
        return popup;
      }
    });
  }

  private void configurePopupRollover(final JCommandButton button) {
    button.getPopupModel().addChangeListener(new ChangeListener() {
      boolean rollover = button.getPopupModel().isRollover();

      @Override
      public void stateChanged(ChangeEvent e) {
        SwingUtilities.invokeLater(new Runnable() {
          @Override
          public void run() {
            boolean isRollover = button.getPopupModel()
                .isRollover();
            if (isRollover == rollover)
              return;

            if (isRollover) {
              // does any *other* button show popup?
              for (JCommandButton bcbButton : buttonStack) {
                if (bcbButton == button)
                  continue;

                if (bcbButton.getPopupModel().isPopupShowing()) {
                  // scroll to view
                  scrollerPanel.scrollToIfNecessary(button
                      .getBounds().x, button.getWidth());
                  // simulate click on the popup area
                  // of *this* button
                  button.doPopupClick();
                }
              }
            }

            rollover = isRollover;
          }
        });
      }
    });
  }

  private void configureBreadcrumbButton(final JCommandButton button) {
    button.setDisplayState(CommandButtonDisplayState.MEDIUM);
    button
        .setPopupOrientationKind(CommandButtonPopupOrientationKind.SIDEWARD);
    button.setHGapScaleFactor(0.75);
    button.getPopupModel().addChangeListener(new ChangeListener() {
      @Override
      public void stateChanged(ChangeEvent e) {
        PopupButtonModel model = button.getPopupModel();
        boolean displayDownwards = model.isRollover()
            || model.isPopupShowing();
        CommandButtonPopupOrientationKind popupOrientationKind = displayDownwards ? CommandButtonPopupOrientationKind.DOWNWARD
            : CommandButtonPopupOrientationKind.SIDEWARD;
        button.setPopupOrientationKind(popupOrientationKind);
      }
    });
  }

  /**
   * Pushes a choice to the top position of the stack. If the current top is
   * already a {@link BreadcrumbItemChoices}, replace it.
   *
   * @param bic
   *            The choice item to push.
   * @return The item that has been pushed.
   */
  protected Object pushChoices(BreadcrumbItemChoices bic) {
    return pushChoices(bic, true);
  }

  /**
   * Pushes a choice to the top position of the stack. If the current top is
   * already a {@link BreadcrumbItemChoices}, replace it.
   *
   * @param bic
   *            The choice item to push.
   * @param toUpdateUI
   *            Indication whether the bar should be repainted.
   * @return The item that has been pushed.
   */
  protected synchronized Object pushChoices(BreadcrumbItemChoices bic,
      boolean toUpdateUI) {
    if (bic == null)
      return null;
    if (modelStack.size() % 2 == 1) {
      modelStack.pop();
    }
    modelStack.addLast(bic);
    if (toUpdateUI) {
      updateComponents();
    }
    return bic;
  }

  /**
   * Pushes an item to the top position of the stack. If the current top is
   * already a {@link BreadcrumbItemChoices}, replace it.
   *
   * @param bi
   *            The item to push.
   * @param toUpdateUI
   *            Indication whether the bar should be repainted.
   * @return The item that has been pushed.
   */
  protected synchronized Object pushChoice(BreadcrumbItem bi,
      boolean toUpdateUI) {
    assert (bi != null);
    Object result;
    // synchronized (stack) {
    if (!modelStack.isEmpty() && modelStack.size() % 2 == 0) {
      modelStack.pop();
    }
    bi.setIndex(modelStack.size());
    modelStack.addLast(bi);
    // }
    return bi;
  }
}
TOP

Related Classes of org.pushingpixels.flamingo.internal.ui.bcb.BasicBreadcrumbBarUI$BreadcrumbBarLayout

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.